#include "WaveObj.h"
#include "MaterialLib.h"

#include <limits>
#include <iostream>

using std::cout;
using std::endl;

WaveObj::WaveObj(istream& is)
{
	m_xmin = m_ymin = m_zmin = 3.40282e+38f;//std::numeric_limits<float>::max();
	m_xmax = m_ymax = m_zmax = -3.40282e+38f;// std::numeric_limits<float>::min();
	
	m_name = "obj_wrapper";
	m_act_material = "default";
	
	m_root = new Assembly;
	m_root->name() = "obj_root";
	m_act_part = m_root;
	parse(is);
	m_root->position().setAll((m_xmax+m_xmin)/-2.0f, (m_ymax+m_ymin)/-2.0f, (m_zmax+m_zmin)/-2.0f);
	
	m_parts.push_back(m_root);
}

WaveObj::~WaveObj()
{
	for (vector<Vertex*>::iterator it = m_vertex.begin(); it != m_vertex.end(); ++it)
	{
		if (NULL != *it)
		{
			delete *it;
		}
	}
	
	for (vector<Vertex*>::iterator it = m_normal.begin(); it != m_normal.end(); ++it)
	{
		if (NULL != *it)
		{
			delete *it;
		}
	}
}

void WaveObj::parse(istream& is)
{
	m_is = &is;
	while (m_is->good())
	{
		getline(*m_is, m_buf);
//		cout << "parsing '" << m_buf << "'" << endl;
		if (m_buf.size() < 2)
		{
			continue;
		}
		
		switch (m_buf[0])
		{
		case 'v':
		case 'V':
			getVs();
			break;
		case 'f':
		case 'F':
			getF();
			break;
		case 'o':
		case 'O':
		case 'g':
		case 'G':
			getO();
			break;
		case 'u':
		case 'U':
			getU();
			break;
		case 'm':
		case 'M':
			getM();
		default:
			break;
		}
	}
}

Face* WaveObj::getFace(stringstream& ss)
{
	Face *face = NULL;
	
	bool normals_given = true;
	
	vector<pair<int, int> > vn;
	
	string token;
	while (ss >> token)
	{
		stringstream tmp(token);
		
		int v[3] = {0, 0, 0};
		int i = 0;
		while (!tmp.eof() && i < 3)
		{
			// try to get the next int
			tmp >> v[i];
			// if failed, check if it was a '/'
			if (tmp.fail() && !(tmp.bad() || tmp.eof()))
			{
				// unset fail-bit
				tmp.clear();
				char c;
				tmp >> c;
				if (c == '/' && i < 3)
				{
					i++;
				}
			}
		}
		v[0]--;
		v[1]--;
		v[2]--;
		
		if (v[2] == -1)
		{
			normals_given = false;
		}
		
		vn.push_back(pair<int, int>(v[0], v[2]));
	}
	
	if (vn.size() > 2)
	{
		bool shared_normal = !normals_given;
		if (normals_given)
		{
			int normal_id = vn.begin()->second;
			if (normal_id == -1)
			{
				shared_normal = false;
			}
			for (vector<pair<int, int> >::iterator it = vn.begin()+1; it != vn.end() && shared_normal; ++it)
			{
				if (normal_id != it->second)
				{
					shared_normal = false;
				}
			}
		}
		
		face = new Face(shared_normal);
		
		if (!normals_given)
		{
			Vertex* norm = new Vertex(0.0f, 0.0f, 0.0f, 0.0f);
			Vertex v0 = *m_vertex[vn[0].first];
			Vertex v1 = *m_vertex[vn[1].first];
			Vertex v2 = *m_vertex[vn[2].first];
			*norm = ((v1-v0)^(v2-v1)).normalize();
			m_normal.push_back(norm);
			
			for (unsigned i = 0; i < vn.size(); ++i)
			{
				const pair<int, int>& ref = vn[i];
				face->push_back(m_vertex[ref.first], norm);
			}
		}
		else
		{
			for (unsigned i = 0; i < vn.size(); ++i)
			{
				const pair<int, int>& ref = vn[i];
				if (!shared_normal)
				{
					face->push_back(m_vertex[ref.first], m_normal[ref.second]);
				}
				else
				{
					face->push_back(m_vertex[ref.first], m_normal[vn[0].second]);
				}
			}
		}
	}
	
	return face;
}

void WaveObj::getF()
{
	stringstream ss(m_buf);
	string tmp;
	
	ss >> tmp;
	if (tmp == "f" || tmp == "F")
	{
		Face* face = getFace(ss);
		if (face != NULL)
		{
			face->material() = m_act_material;
//			cout << "face " << face << " got material " << m_act_material << endl;

			m_act_part->addFace(face);
		}
	}
}

void WaveObj::getO()
{
	stringstream ss(m_buf);
	string tmp;
	
	ss >> tmp;
	if (tmp == "g" || tmp == "G" || tmp == "o" || tmp == "O")
	{
		ss >> tmp;
		
		Assembly* part = new Assembly;
		part->name() = tmp;
		part->material() = m_act_material;
		
		m_root->addPart(part);
		m_act_part = part;
	}
}

void WaveObj::getU()
{
	stringstream ss(m_buf);
	string tmp;
	
	ss >> tmp;
	if (tmp == "usemtl")
	{
		ss >> m_act_material;
		m_act_part->material() = m_act_material;
	}
}

void WaveObj::getVs()
{
	stringstream ss(m_buf);
	string tmp;
	
	ss >> tmp;
	m_buf.erase(0, 2);
	if (tmp == "v" || tmp == "V")
	{
		getV();
	}
	else if (tmp == "vn" || tmp == "VN")
	{
		getVN();
	}
}

void WaveObj::getV()
{
	stringstream ss(m_buf);
	
	float x, y, z;
	ss >> x >> y >> z;
	
	if (m_xmin > x)
	{
		m_xmin = x;
	}
	
	if (m_xmax < x)
	{
		m_xmax = x;
	}
	
	if (m_ymin > y)
	{
		m_ymin = y;
	}
	
	if (m_ymax < y)
	{
		m_ymax = y;
	}
	
	if (m_zmin > z)
	{
		m_zmin = z;
	}
	
	if (m_zmax < z)
	{
		m_zmax = z;
	}
	
	m_vertex.push_back(new Vertex(x, y, z));
}

void WaveObj::getVN()
{
	m_buf.erase(0, 1);
	stringstream ss(m_buf);
	
	float x, y, z;
	ss >> x >> y >> z;
	
	m_normal.push_back(new Vertex(x, y, z, 0.0f));
}

void WaveObj::getM()
{
	stringstream ss(m_buf);
	string tmp;
	
	ss >> tmp;
	if (tmp == "mtllib" || tmp == "MTLLIB")
	{
		ss >> tmp;
		cout << "using MLIB: '" << tmp << "'" << endl;
		MaterialLib::getInstance()->import(tmp);
	}
}

